package icasue.net.protocol.http;

import icasue.net.base.annotations.NotNull;
import icasue.net.base.annotations.Nullable;
import icasue.net.base.utils.IOUtil;
import icasue.net.base.utils.UnicodeUtil;
import icasue.net.protocol.http.common.Const;
import icasue.net.protocol.http.enums.HttpStatus;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

import static icasue.net.protocol.http.common.Const.*;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/3/12 下午4:06
 * @UpdateDate:
 * @Description:
 */
public class HttpHelper {

    private static final Charset UTF_8 = Charset.forName("UTF-8");
    private static final Charset CHARSET_DEFAULT = Charset.defaultCharset();

    public static HttpResponse<String> parseInternal(@NotNull InputStream inStream, @Nullable Charset charset) throws IOException {
        if(charset == null){
            charset = CHARSET_DEFAULT;
        }
        HttpResponse<String> response = new HttpResponse<>();
        //Http protocol net state.
        String protocolState = IOUtil.readLine(inStream,UTF_8,'\r');
        if(protocolState.trim().isEmpty()){
            throw new IOException("failed to respond");
        }
        String[] version_state = protocolState.trim().split(SPACE);
        response.setVersion(version_state[0]);
        response.setState(HttpStatus.resolve(Integer.parseInt(version_state[1])));
        //Http protocol response headers.
        Map<String,String> headers = new LinkedHashMap<>();
        response.setHeaders(headers);
        //Line.
        String line = null;
        //Response Data buffer.
        int dataLen = -1;
        while ((line = IOUtil.readLine(inStream,UTF_8,Const._r)) != null && isNotEmpty(line)){
            String key = line.substring(0, line.indexOf($COLON));
            String val = line.substring(key.length() + 1).trim();
            headers.put(key,val);
            if(key.equals(Const.HEADER_CONTENT_LENGTH)){
                dataLen = Integer.valueOf(val);
            }
        }
        StringBuffer data = new StringBuffer();

        //Response's content encoding, perhaps servers response has been compressed.
        String contentEncoding = Optional.ofNullable(headers.get(Const.HEADER_CONTENT_ENCODING_LOWER))
                .orElseGet(() -> headers.get(Const.HEADER_CONTENT_ENCODING_UPPER));
        //Exist Content-Length Clearly, then full response buffer, and parse response.
        if(dataLen > -1){
            //Full response buffer.
            byte[] content = IOUtil.readLength(inStream, dataLen);
            if(contentEncoding != null){
                content = unCompress(content,contentEncoding);
            }
            data.append(new String(content, charset));
        }else {
            //Transfer-Encoding: chunked, need to combine data until block which size of zero.
            String blockSizeHex = null;
            ByteArrayOutputStream byteStream = contentEncoding == null ? null : new ByteArrayOutputStream();
            while ((blockSizeHex = IOUtil.readLine(inStream,charset,Const._r)) != null
                    && !blockSizeHex.equals(Const.BLOCK_SIZE_ZERO)){
                //For data block, data package will mark delimiter as [size\r\n content\n\n \r\n]
                if(isEmpty(blockSizeHex)){ continue; }
                //Read block's data.
                byte[] content = IOUtil.readLength(inStream, Integer.parseInt(blockSizeHex,16));
                // write compress bytes to buffer stream, cause of depress need complicate compress package data.
                if(contentEncoding != null){
                    byteStream.write(content);
                    byteStream.flush();
                }else {
                    data.append(new String(content, charset));
                }
            }
            if(byteStream != null && byteStream.size() > 0){
                byteStream.close();
                byte[] depressBytes = unCompress(byteStream.toByteArray(), contentEncoding);
                data.append(new String(depressBytes,charset));
            }
        }
        response.setData(data.toString());
        return response;
    }

    private static byte[] unCompress(byte[] bytes,String encodeType){
        if(bytes == null || bytes.length == 0 || isEmpty(encodeType)){
            return bytes;
        }
        switch (encodeType){
            case Const.ENCODER_TYPE_GZIP:
                bytes = UnicodeUtil.unGzip(bytes);
                break;
            case Const.ENCODER_TYPE_DEFLATE:
                bytes = UnicodeUtil.unDeflate(bytes);
                break;
            case Const.ENCODER_TYPE_BR:
                break;
            case Const.ENCODER_TYPE_DEFAULT:
                break;
            default:
                break;
        }
        return bytes;
    }

    private static boolean isEmpty(final CharSequence cs) {
        return cs == null || cs.length() == 0;
    }

    private static boolean isNotEmpty(final CharSequence cs) {
        return !isEmpty(cs);
    }
}
